#include <core/types.h>
#include <core/io.h>
#include <core/init.h>
#include <core/errno.h>
#include <core/timer.h>
#include <core/irq.h>
#include <autoconf.h>
#include <net/net.h>
#include <arch/arm.h> // fixme
#include "cs8900.h"

#define VENDOR_ID_CS  0x630E
#define CS8900_DRV_NAME "cs8900 driver"

#define CS8900_INW(reg) ReadShort(CS8900_IOBASE + reg)
#define CS8900_OUTW(reg, val) WriteShort(val, CS8900_IOBASE + reg)

#define cs8900io_readw(port) \
	(CS8900_OUTW(ADDR_PORT, port), CS8900_INW(DATA_PORT))

#define cs8900io_writew(port, val) \
	do { \
		CS8900_OUTW(ADDR_PORT, port); \
		CS8900_OUTW(DATA_PORT, val); \
	} while (0)

struct cs8900_dev {
	int irq;
};

static int isTxOk = 0;

static void  CS8900Reset(void)
{
	cs8900io_writew(PP_SelfCTL, cs8900io_readw(PP_SelfCTL) | POWER_ON_RESET);
	udelay(30);
	if ((cs8900io_readw(PP_SelfST) & INIT_DONE) == 0)
		printf("cs89x0 reset timeout!\n");
}

static int CS8900SendPacket(struct NetDevice *pNetDev, struct SocketBuffer *pSockBuff)
{
	const UINT16 *wbuf;
	int length;
	UINT32 nLen;
	int count;
	ULONG ulFlag;

	LockIrqPsr(ulFlag);

	isTxOk = 0;

	nLen = pSockBuff->wSkbSize;
	wbuf = (const UINT16 *)pSockBuff->pSkbData; // fixme

	CS8900_OUTW(TX_CMD_PORT, TX_NOW);
	CS8900_OUTW(TX_LEN_PORT, nLen);

	while ((cs8900io_readw(PP_BusST) & READY_FOR_TX_NOW) == 0) {
		printf("%s(): wait ready\n", __FUNCTION__);
	}

	length = nLen + 1;
	while (length > 0) {
		CS8900_OUTW(TX_FRAME_PORT, *wbuf);
		wbuf++;
		length -= 2;
	}

	count = 0;
	while (!isTxOk && count < 20)
	{
		count++;
	}

	UnlockIrqPsr(ulFlag);

	return 0;
}

static int CS8900Isr(void *pDev)
{
	UINT16 status;

	while ((status = CS8900_INW(ISQ_PORT))) {
		int i;
		UINT16 wRxStat, wRxLen;
		UINT16 *pRxData;
		struct SocketBuffer *pSockBuff;

		switch (status & ISQ_EVENT_MASK)
		{
		case ISQ_RECEIVER_EVENT:
			wRxStat = CS8900_INW(TX_FRAME_PORT);
			wRxLen  = CS8900_INW(TX_FRAME_PORT);

			pSockBuff = GkSockBuffAlloc(0, wRxLen);

			if (NULL == pSockBuff)
				return -ENOMEM;

			pRxData = (UINT16 *)pSockBuff->pSkbData;

#ifdef NET_DEBUG
			printf("wRxLen=0x%08x\n", wRxLen);
#endif

			i = 0;
			while (i < wRxLen)
			{
				*pRxData = CS8900_INW(TX_FRAME_PORT);
				i += 2;
#ifdef NET_DEBUG
				printf("%02x%02x", *(char *)pRxData, *((char *)pRxData + 1));
				if ((i & 127) == 0) putchar('\n');
#endif
				pRxData++;
			}

#ifdef NET_DEBUG
			putchar('\n');
#endif

			GkNetRxDcp(pSockBuff);

			break;

		case ISQ_TRANSMITTER_EVENT:
			isTxOk = 1;
			break;

		default:
			// TODO:  add code here
			break;
		}
	}

	return 0;
}

static int CS8900SetMacAddr(struct NetDevice *pNetDev, const BYTE *pbMacAddr)
{
	int i;

	for (i = 0; i < MAC_ADDR_LEN; i += 2)
	{
		pNetDev->vbMacAddr[i] = pbMacAddr[i];
		pNetDev->vbMacAddr[i + 1] = pbMacAddr[i + 1];

		cs8900io_writew(PP_IA + i, *(UINT16 *)(pNetDev->vbMacAddr + i));
	}
}

static int __INIT__ CS8900Init(void)
{
	int ret = 0, i;
	UINT16 id1;
	UINT16 val;
	struct NetDevice *pNetDev;

	id1 = cs8900io_readw(PP_ChipID);
	if (id1 != VENDOR_ID_CS) {
		printf("CS8900 NOT found!\n");
		ret = -ENODEV;
		goto L4;
	}

	printf("CS8900 is found\n");

	CS8900Reset();

	pNetDev = GkNetDevNew();

	ret = RegisterIsr(IRQ_EINT9, CS8900Isr, NULL);

	val = cs8900io_readw(PP_LineCTL);
	cs8900io_writew(PP_LineCTL, val | SERIAL_RX_ON | SERIAL_TX_ON);

	cs8900io_writew(PP_RxCTL, DEF_RX_ACCEPT);
	cs8900io_writew(PP_RxCFG, RX_OK_ENBL | RX_CRC_ERROR_ENBL);

	val = cs8900io_readw(PP_TxCFG);
	cs8900io_writew(PP_TxCFG, val | TX_OK_ENBL);

	cs8900io_writew(PP_BufCFG, READY_FOR_TX_ENBL);
	cs8900io_writew(PP_CS8900_ISAINT, 0);
	cs8900io_writew(PP_BusCTL, ENABLE_IRQ);

	pNetDev->SendPacket = CS8900SendPacket;
	pNetDev->SetMacAddr = CS8900SetMacAddr;

	return GkNetDevRegister(pNetDev);
L4:
L3:
L2:
L1:
	free(pNetDev);
	return ret;
}

DRIVER_INIT(CS8900Init);

